/**
 * 
 */
package cn.edu.cqu.bysj.wm.keynode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.neo4j.graphalgo.CommonEvaluators;
import org.neo4j.graphalgo.CostAccumulator;
import org.neo4j.graphalgo.CostEvaluator;
import org.neo4j.graphalgo.impl.centrality.BetweennessCentrality;
import org.neo4j.graphalgo.impl.centrality.NetworkDiameter;
import org.neo4j.graphalgo.impl.centrality.StressCentrality;
import org.neo4j.graphalgo.impl.shortestpath.SingleSourceShortestPath;
import org.neo4j.graphalgo.impl.shortestpath.SingleSourceShortestPathDijkstra;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.ujmp.core.Matrix;

import bysj.cqu.wm.key.MyClosenessCentrality;
import bysj.cqu.wm.key.MyCostAccumulator;
import bysj.cqu.wm.key.MyCostDivider;

import cn.edu.cqu.bysj.wm.ago.lp.LP;
import cn.edu.cqu.bysj.wm.def.ConstDef;
import cn.edu.cqu.bysj.wm.def.PropertyDef;
import cn.edu.cqu.bysj.wm.def.RelTypeDef;
import cn.edu.cqu.bysj.wm.def.Weight;
import cn.edu.cqu.bysj.wm.neo4j.Neo4jTraversal;
import cn.edu.cqu.bysj.wm.util.MapComparator1;

/**
 * @author Meng
 * 
 */
public class CentralityTool {
	
	public static String[] title = new String[6];

	public GraphDatabaseService gdb;
	public List<Node> nodes;
	public int n;
	public Direction direction;
	public Weight weight;
	public Matrix m;

	public int[][] rank;
	public double[][] rankValue;
	public long[][] rankNode;

	public Map<Node, Double> outdegree = new LinkedHashMap<Node, Double>();
	public Map<Node, Double> indegree = new LinkedHashMap<Node, Double>();
	public Map<Node, Double> betweenness = new LinkedHashMap<Node, Double>();
	public Map<Node, Double> stress = new LinkedHashMap<Node, Double>();
	public Map<Node, Double> closeness = new LinkedHashMap<Node, Double>();;
	public Map<Node, Double> cluster = new LinkedHashMap<Node, Double>();
	public Map<Node, Double> pagerank = new LinkedHashMap<Node, Double>();

	private SingleSourceShortestPath<Integer> ssspDijsktra;
	// private SingleSourceShortestPath<Integer> ssspBFS;

	private MyCostDivider<Integer> costDivider;
	private CostEvaluator<Integer> costEvaluator;
	private CostAccumulator<Integer> costAccumulator;
	private Comparator<Integer> costComparator;

	private int diameter;
	public int[] flag;

	/**
	 * @param lp
	 * @param flag
	 */
	public CentralityTool(LP lp, int[] flag) {
		super();
		this.n = lp.nodes.size();
		this.m = lp.matrix;
		this.nodes = lp.nodes;
		this.gdb = lp.gdb;
		this.nodes = lp.nodes;
		this.direction = lp.direction;
		this.weight = lp.weight;
		this.flag = flag;

		title[ConstDef.DEGREE] = "degree";
		title[ConstDef.BETWEENNESS] = "betweenness";
		title[ConstDef.STRESS] = "stress";
		title[ConstDef.CLOSENESS] = "closeness";
		title[ConstDef.CLUSTER] = "cluster";
		title[ConstDef.PAGERANK] = "pagerank";

		ssspDijsktra();
		getDiameter();
		rankAll();
	}

	/**
	 * @param lp
	 * @param flag
	 * @param list
	 */
	public CentralityTool(LP lp, int[] flag, List<Node> list) {
		super();
		this.n = lp.nodes.size();
		this.m = lp.matrix;
		this.nodes = lp.nodes;
		this.gdb = lp.gdb;
		this.nodes = list;
		this.direction = lp.direction;
		this.weight = lp.weight;
		this.flag = flag;

		title[ConstDef.DEGREE] = "degree";
		title[ConstDef.BETWEENNESS] = "betweenness";
		title[ConstDef.STRESS] = "stress";
		title[ConstDef.CLOSENESS] = "closeness";
		title[ConstDef.CLUSTER] = "cluster";
		title[ConstDef.PAGERANK] = "pagerank";

		ssspDijsktra();
		getDiameter();
		rankAll();
	}

	/**
	 * get the diameter in graph,here used in closeness centrality
	 */
	public void getDiameter() {
		NetworkDiameter<Integer> netDiameter = new NetworkDiameter<Integer>(
				ssspDijsktra, 0, new HashSet<Node>(nodes), costComparator);

		diameter = netDiameter.getCentrality(null);
	}

	/**
	 * Degree Centrality : according to out going degree or incoming degree for
	 * directed graph and common degree for undirected graph
	 */
	public void degreeCentrality() {

		// out degree
		for (Node node : nodes) {
			double out = 0, in = 0;

			if (direction.equals(Direction.BOTH)) {
				for (@SuppressWarnings("unused")
				Relationship rel : node.getRelationships(Direction.BOTH)) {
					out++;
					in++;
				}
				outdegree.put(node, out);
				indegree.put(node, in);
			} else {

				for (@SuppressWarnings("unused")
				Relationship rel : node.getRelationships(Direction.OUTGOING)) {
					out++;
				}
				outdegree.put(node, out);
				for (@SuppressWarnings("unused")
				Relationship rel : node.getRelationships(Direction.INCOMING)) {
					in++;
				}
				indegree.put(node, in);
			}
		}
	}

	/**
	 * Betweenness Centrality : according Linton C. Freeman(1977) using Ulrik
	 * Brandes's method to calculate betweenness
	 */
	public void betweennessCentrality() {

		BetweennessCentrality<Integer> bc = new BetweennessCentrality<Integer>(
				ssspDijsktra, new HashSet<Node>(nodes));

		for (Node node : nodes) {
			betweenness.put(node, bc.getCentrality(node));
		}

	}

	/**
	 * Stress Centrality : the number of shortest paths going through each node
	 */
	public void stressCentrality() {

		StressCentrality<Integer> sc = new StressCentrality<Integer>(
				ssspDijsktra, new HashSet<Node>(nodes));

		for (Node node : nodes) {
			stress.put(node, sc.getCentrality(node));
		}

	}

	/**
	 * Closeness Centrality : "average" distance from every node to all other
	 * nodes
	 */
	public void clossnessCentrality() {

		MyCostAccumulator<Integer> myCostAccumulator = new MyCostAccumulator<Integer>() {

			@Override
			public Integer addCosts(Integer c1, Integer c2) {
				if (c1 == null) {
					return c2;
				}
				return c1 + c2;
			}

			@Override
			public Integer addCosts(Integer c1, int c2) {
				if (c1 == null) {
					return c2;
				}
				return c1 + c2;
			}
		};

		MyClosenessCentrality<Integer> cc = new MyClosenessCentrality<Integer>(
				ssspDijsktra, myCostAccumulator, 0, new HashSet<Node>(nodes),
				costDivider, diameter);

		for (Node node : nodes) {
			closeness.put(node, cc.getCentrality(node));
		}

	}

	/**
	 * Cluster Coefficient : refects how closed the nodes in the net are
	 * connected
	 * 
	 * @param limit
	 *            to filter the unimportant nodes
	 */
	public void clusterCoefficient(double limit) {

		for (Node node : nodes) {

			// conected nodes to node
			List<Node> list = Neo4jTraversal.getAdjoinedNodes(node, direction);
			int k = list.size();

			if (k <= limit) {
				cluster.put(node, 0d);
				continue;
			}
			// calculate actually exist edges in the local net from node
			if (list.size() > 1) {

				int num = Neo4jTraversal.getEdgesInAdjoinedNodes(node, list)
						.size();

				// cluster coefficient definition
				double cc = 2d * num / (k * (k - 1));
				// for directed graph
				if (!direction.equals(Direction.BOTH)) {
					cc /= 2;
				}
				cluster.put(node, cc);
			} else {
				cluster.put(node, 0d);
			}
		}

	}

	/**
	 * PageRank: use the idea of pagerank to calculate the rank of nodes
	 * 
	 * @param m
	 *            matrix of the network
	 */
	public void pagerank(Matrix m) {

		Map<Node, Double> temp = new HashMap<Node, Double>();

		double pro = 0.5;
		for (Node node : nodes) {
			pagerank.put(node, 1d);
			temp.put(node, 0d);
		}
		// judge whether to stop iteration
		while (!limit(temp)) {
			for (Node node : nodes) {
				temp.put(node, pagerank.get(node));
			}
			for (Node node1 : nodes) {
				pagerank.put(node1, 0d);
				for (Node node2 : nodes) {
					// direction is incoming
					if (direction.equals(Direction.INCOMING)) {
						if (m.getAsInt(node2.getId() - 1, node1.getId() - 1) > 0) {
							double pr = pagerank.get(node1)
									+ pro
									* (pagerank.get(node2) / outdegree
											.get(node2));
							pagerank.put(node1, pr);
						}
					} else {// direction is outgoing or both
						if (m.getAsInt(node1.getId() - 1, node2.getId() - 1) > 0) {
							double pr = pagerank.get(node1)
									+ pro
									* (pagerank.get(node2) / indegree
											.get(node2));
							pagerank.put(node1, pr);
						}
					}
				}
				pagerank.put(node1, pagerank.get(node1) + (1 - pro));
			}
		}

	}

	/**
	 * judge whether the process of pagerank can stop or not
	 * 
	 * @param temp
	 *            temp map of pagerank of each node
	 * @return true or false
	 */
	private boolean limit(Map<Node, Double> temp) {
		double limit = 0.000000001;

		for (Node node : nodes) {
			if (Math.abs(pagerank.get(node) - temp.get(node)) > limit) {
				return false;
			}
		}
		return true;
	}

	/**
	 * rank all the index of nodes and get the matrix of
	 * rank[][],rankNode[][],rankValue[][]
	 */
	public void rankAll() {

		rank = new int[n][6];
		rankValue = new double[n][6];
		rankNode = new long[n][6];

		if (direction.equals(Direction.INCOMING)) {
			if (flag[0] == 1)
				degreeCentrality();
			rank(indegree);
		} else {
			if (flag[0] == 1)
				degreeCentrality();
			rank(outdegree);
		}
		if (flag[1] == 1) {
			betweennessCentrality();
			rank(betweenness);
		}
		if (flag[2] == 1) {
			stressCentrality();
			rank(stress);
		}
		if (flag[3] == 1) {
			clossnessCentrality();
			rank(closeness);
		}
		if (flag[4] == 1) {
			clusterCoefficient(n * 0.006);
			rank(cluster);
		}
		if (flag[5] == 1) {
			pagerank(m);
			rank(pagerank);
		}
		int i = 0;
		for (Node node1 : nodes) {

			if (direction.equals(Direction.INCOMING) && flag[0] == 1) {
				int pos = 0;
				for (Node node2 : indegree.keySet()) {
					if (node2.equals(node1)) {
						rank[i][0] = pos;
						rankValue[pos][0] = indegree.get(node2);
						rankNode[pos][0] = node2.getId();
						break;
					}
					pos++;
				}
			} else if (flag[0] == 1) {
				int pos = 0;
				for (Node node2 : outdegree.keySet()) {
					if (node2.equals(node1)) {
						rank[i][0] = pos;
						rankValue[pos][0] = outdegree.get(node2);
						rankNode[pos][0] = node2.getId();
						break;
					}
					pos++;
				}
			}
			if (flag[1] == 1) {
				int pos = 0;
				for (Node node2 : betweenness.keySet()) {
					if (node2.equals(node1)) {
						rank[i][1] = pos;
						rankValue[pos][1] = betweenness.get(node2);
						rankNode[pos][1] = node2.getId();
						break;

					}
					pos++;
				}
			}
			if (flag[2] == 1) {
				int pos = 0;
				for (Node node2 : stress.keySet()) {
					if (node2.equals(node1)) {
						rank[i][2] = pos;
						rankValue[pos][2] = stress.get(node2);
						rankNode[pos][2] = node2.getId();
						break;
					}
					pos++;
				}
			}
			if (flag[3] == 1) {
				int pos = 0;
				for (Node node2 : closeness.keySet()) {
					if (node2.equals(node1)) {
						rank[i][3] = pos;
						rankValue[pos][3] = closeness.get(node2);
						rankNode[pos][3] = node2.getId();
						break;
					}
					pos++;
				}
			}
			if (flag[4] == 1) {
				int pos = 0;
				for (Node node2 : cluster.keySet()) {
					if (node2.equals(node1)) {
						rank[i][4] = pos;
						rankValue[pos][4] = cluster.get(node2);
						rankNode[pos][4] = node2.getId();
						break;
					}
					pos++;
				}
			}
			if (flag[5] == 1) {
				int pos = 0;
				for (Node node2 : pagerank.keySet()) {
					if (node2.equals(node1)) {
						rank[i][5] = pos;
						rankValue[pos][5] = pagerank.get(node2);
						rankNode[pos][5] = node2.getId();
						break;
					}
					pos++;
				}
			}
			i++;
		}

	}

	/**
	 * rank the map
	 * 
	 * @param map
	 *            map recording the node and index value
	 */
	private void rank(Map<Node, Double> map) {

		List<Map.Entry<Node, Double>> list = new ArrayList<Map.Entry<Node, Double>>(
				map.entrySet());
		Collections.sort(list, new MapComparator1());
		map.clear();
		for (Map.Entry<Node, Double> entry : list) {
			map.put(entry.getKey(), entry.getValue());
		}

	}

	/**
	 * spearman coefficient
	 * 
	 * @param x1
	 *            index one
	 * @param x2
	 *            index two
	 * @return value of coefficient betweenness 0 and 1
	 */
	public double spearman(int x1, int x2) {
		double sum = 0, res;
		for (int i = 0; i < n; i++) {
			sum = (rank[i][x1] - rank[i][x2]) * (rank[i][x1] - rank[i][x2])
					+ sum;
		}
		res = 1 - (6 * sum) / (n * n * n - n);
		return res;
	}

	/**
	 * use BFS in single source shortest path,unweighted graph
	 */
	public void ssspBFS() {
		// ssspBFS = new SingleSourceShortestPathBFS(nodes.get(2), direction,
		// RelTypeDef.EDGE);
	}

	/**
	 * use dijsktra in single source shortest path,weighted graph
	 */
	public void ssspDijsktra() {
		// define cost divider
		costDivider = new MyCostDivider<Integer>() {

			@Override
			public Double divideCost(Integer c, Double d) {
				return c / d;
			}

			@Override
			public Double divideByCost(Double d, Integer c) {
				return d / c;
			}
		};
		// define cost evaluator
		costEvaluator = CommonEvaluators.intCostEvaluator(PropertyDef.WEIGHT);

		// define cost accumulator
		costAccumulator = new CostAccumulator<Integer>() {

			@Override
			public Integer addCosts(Integer c1, Integer c2) {
				return c1 + c2;
			}

		};

		// define comparator
		costComparator = new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				if (o2 == null) {
					return 1;
				}
				if (o1 > o2)
					return 1;
				if (o1 < o2)
					return -1;
				return 0;
			}
		};

		ssspDijsktra = new SingleSourceShortestPathDijkstra<Integer>(0,
				nodes.get(2), costEvaluator, costAccumulator, costComparator,
				direction, RelTypeDef.EDGE);
	}
}
